package model;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;

import shared.Buffer;
import shared.ConcreteLock;
import shared.Lock;

/**
 * The Product class is a component with structure.
 * 
 * @author hfi411pi
 *
 */
public class Product extends ComponentCommon {

	private static final String CycleMessage = "Zyklen sind in der Aufbaustruktur nicht erlaubt!";

	private final HashMap<String,QuantifiedComponent> components;

	private HashMap<String,QuantifiedComponent> getComponents() {
		return this.components;
	}
	
	/**
	 * Factory method for creating Products.
	 * @param name is the name of the product.
	 * @param price is the price of the product.
	 * @return the created Product
	 */
	public static Product create(final String name, final int price) {
		return new Product(name,price, new HashMap<String, QuantifiedComponent>());
	}
	/**
	 * Constructor of Product. * 
	 * @param name is the name of the product.
	 * @param price is the price of the product.
	 * @param components is the a HashMap of all parts of this Product.
	 */
	protected Product(final String name, final int price, final HashMap<String,QuantifiedComponent> components) {
		super(name,price);
		this.components = components;
	}

	/** Adds a part to this product.
	 * @param part is the part to add.
	 * @param int is the quantity of the part to add.
	 * @throws Exception if this is a part of the part that should be added.
	 */
	@Override
	public void addPart(final ComponentCommon part, final int amount) throws Exception{
		if (part.contains(this))throw new Exception(CycleMessage);
		final String partName = part.getName();
		if (this.getComponents().containsKey(partName)){
			final QuantifiedComponent oldQuantification = this.getComponents().get(partName); 
			oldQuantification.setQuantity(oldQuantification.getQuantity() + amount);
		}else{
			this.getComponents().put(partName, QuantifiedComponent.createQuantifiedComponent(amount, part));
		}
		
		part.register(this.getObserver());
		
		this.getState().refresh(StructureChangedEvent.create()); 
	}


	/** Provides true if the provided component is already a part of this product.
	 * @param component is the Component to look for.
	 * @return true if component is a part of this. Else returns false.
	 */
	@Override
	public boolean containsTransitive(final ComponentCommon component) {
		final Iterator<QuantifiedComponent> i = this.getComponents().values().iterator();
		while (i.hasNext()){
			final QuantifiedComponent current = i.next();
			if (current.contains(component))return true;
		}
		return false;
	}

	/** Provides a Vector of all direct parts that are required by this product.
	 * @return a Vector with all direct parts of this product.
	 */
	@Override
	public Vector<QuantifiedComponent> getDirectParts() {
		return new Vector<QuantifiedComponent>(this.getComponents().values());
	}

	/** Provides the amount of materials that are required by this product.
	 * @return the amount of materials.
	 */
	@Override
	public int getNumberOfMaterials() {
		int result = 0;
		final Iterator<QuantifiedComponent> i = this.getComponents().values().iterator();
		while (i.hasNext()){
			final QuantifiedComponent current = i.next();
			result = result + current.getNumberOfMaterials();
		}
		return result;
	}

	/** Provides the price of this product and all its parts.
	 * @return the overall price of this product.
	 */
	@Override
	public int calculateNewOverallPriceTransitive() {
		final Buffer<Integer> resultBuffer = new Buffer<Integer>();
		for (final QuantifiedComponent component : this.getComponents().values()) {
			new Thread(new Obtainer<Integer>(component, resultBuffer) {

				@Override
				protected Integer calculate() {
					return this.getComponent().getQuantity() * this.getComponent().getComponent().getOverallPrice();
				}
			}).start();
		}
		int price = 0;
		for (int i = 0; i < this.getComponents().values().size(); i++) {
			price+=resultBuffer.get();
		}
		return price;
	}
	
	/** Provides a MaterialList of all materials that are required by this product.
	 * @return the MaterialList of this Product.
	 * */
	@Override
	public MaterialList getMaterialList() {
		this.materialListLock.lock();
		final MaterialList result = this.getState().getMaterialList();
		this.materialListLock.unlock();
		return result;
	}
	
	Lock materialListLock = new ConcreteLock(false);
	
	@Override
	public MaterialList calculateNewMaterialList(){
		final Buffer<MaterialList> resultBuffer = new Buffer<MaterialList>();
		for(final QuantifiedComponent component : this.getComponents().values()){
			new Thread(new Obtainer<MaterialList>(component, resultBuffer) {

				@Override
				protected MaterialList calculate() {
					final MaterialList newMaterialList = this.getComponent().getComponent().getMaterialList();
					return newMaterialList.multiply(this.getComponent().getQuantity());
				}
			}).start();
		}
		
		MaterialList resultMaterialList = MaterialList.create();
		for(int i = 0; i < this.getComponents().size(); i++){
			resultMaterialList = resultMaterialList.merge(resultBuffer.get());
		}
		return resultMaterialList;
	}
}